package io.gitee.devaper.starlight.module.room.websocket

import com.fasterxml.jackson.databind.ObjectMapper
import io.gitee.devaper.starlight.common.entity.vo.*
import io.gitee.devaper.starlight.common.util.*
import io.gitee.devaper.starlight.module.room.*
import io.gitee.devaper.starlight.module.room.constant.*
import io.gitee.devaper.starlight.module.room.entity.vo.*
import io.gitee.devaper.starlight.module.room.websocket.event.server.*
import io.gitee.devaper.starlight.security.util.*
import io.gitee.devaper.starlight.websocket.*
import org.slf4j.*
import org.springframework.http.server.*
import org.springframework.security.core.Authentication
import org.springframework.web.socket.*
import org.springframework.web.socket.handler.TextWebSocketHandler
import org.springframework.web.socket.server.HandshakeInterceptor
import java.lang.Exception

private const val RID_ATTRIBUTE_NAME = "rid"
private const val PWD_ATTRIBUTE_NAME = "pwd"

@WebSocketController(["/room"], allowedOriginPatterns = ["*"])
open class RoomWebSocket(
    private val objectMapper: ObjectMapper,
    private val roomRepository: RoomRepository,
    private val clientMessageHandler: ClientMessageHandler
) : TextWebSocketHandler(), HandshakeInterceptor {

    override fun afterConnectionEstablished(session: WebSocketSession) {
        // 检查房间是否存在
        val room = session.tryAcquireRoom() ?: let {
            session.close(WS_ROOM_CLOSED_CLOSE_STATUS)
            return
        }

        room.write {
            // 检查房间是否满人
            if (room.clients.size >= room.capacity) {
                session.close(WS_ROOM_MEMBER_MAXIMUM_CLOSE_STATUS)
                return
            }

            // 添加到房间
            room.clients.add(session)

            handleUserJoin(room, session)
        }
    }


    override fun afterConnectionClosed(session: WebSocketSession, status: CloseStatus) {
        // 房间解散了就不需要通知别人离开了
        if (status == WS_ROOM_CLOSED_CLOSE_STATUS) {
            return
        }
        session.tryAcquireRoom()?.let { room ->
            room.write {
                // 从房间移除
                room.clients.remove(session)

                handleUserLeave(room, session)
            }
        }
    }

    override fun handleTextMessage(session: WebSocketSession, message: TextMessage) {
        val user = session.principal.tryAcquireUser()!!
        val room = session.tryAcquireRoom() ?: return

        val tree = objectMapper.readTree(message.payload)
        val type = tree.get("type")?.takeIf { it.isTextual }!!
        val details = tree.get("details")?.takeIf { it.isObject }!!

        clientMessageHandler.handle(
            ClientMessageHandler.Context(
                room = room,
                sender = session,
                senderUser = user,
                type = type.textValue(),
                details = details
            )
        )
    }

    override fun beforeHandshake(
        request: ServerHttpRequest,
        response: ServerHttpResponse,
        wsHandler: WebSocketHandler,
        attributes: MutableMap<String, Any>
    ): Boolean {
        // 检查是否已经认证
        val authentication = request.principal as? Authentication
        if (authentication == null || !authentication.isAuthenticated) {
            return false
        }

        // 取出提交的参数
        val params = parseRawQuery(request.uri.rawQuery)
        val rid = params[RID_ATTRIBUTE_NAME]?.firstOrNull() // 房间号
        val pwd = params[PWD_ATTRIBUTE_NAME]?.firstOrNull() // 房间密码
        if (rid.isNullOrEmpty()) {
            return false
        }

        // 将房间号存储到attributes中, 方便后续取出
        attributes[RID_ATTRIBUTE_NAME] = rid
        // 检查房间是否存在
        val room = roomRepository.map[rid] ?: return false
        // 检查密码是否正确
        return pwd?.takeIf { it.isNotEmpty() } == room.password
    }

    override fun afterHandshake(
        request: ServerHttpRequest,
        response: ServerHttpResponse,
        wsHandler: WebSocketHandler,
        exception: Exception?
    ) {
    }

    private fun handleUserJoin(room: Room, session: WebSocketSession) {
        if (session.isOpen) {
            val user = session.principal.tryAcquireUser()!!
            // 向新加入的用户发送房间信息
            session.sendMessage(
                TextMessage(
                    objectMapper.writeValueAsString(
                        ServerEvent(
                            details = RoomInitServerEventDetails(
                                room = RoomVO.valueOf(room)
                            )
                        )
                    )
                )
            )
            // 为其他用户广播新加入的用户信息
            room.broadcast(
                message = TextMessage(
                    objectMapper.writeValueAsString(
                        ServerEvent(
                            details = UserJoinServerEventDetails(
                                user = UserVO.valueOf(user)
                            )
                        )
                    )
                ),
                ignore = session,
                useLock = true
            )
        }
    }

    private fun handleUserLeave(room: Room, session: WebSocketSession) {
        val user = session.principal.tryAcquireUser()!!
        // 广播用户离开的信息
        room.broadcast(
            message = TextMessage(
                objectMapper.writeValueAsString(
                    ServerEvent(
                        details = UserLeaveServerEventDetails(
                            user = user.id
                        )
                    )
                )
            ),
            ignore = session,
            useLock = true
        )
    }


    private fun WebSocketSession.tryAcquireRoom(): Room? {
        return roomRepository.map[this.attributes[RID_ATTRIBUTE_NAME] ?: return null]
    }

    companion object {
        val log = LoggerFactory.getLogger(RoomWebSocket::class.java)
    }
}

